<?php
/**
 * The phase class.
 * @author Ian Dunn <scholar42@gmail.com>
 * @version 1.0
 * @since 2013-11-02
 */
require_once 'database.php';
require_once 'table.php';
require_once 'materials.php';
require_once 'order.php';
require_once 'schedule.php';

class phase extends table {

	const STATUS_WAITING = 0;
	const STATUS_RUNNING = 1;
	const STATUS_FINISHED = 2;

	var $machId;
	var $time_req;
	var $ordId;
	var $priority;
	var $deadline;
	var $keyword;
	var $order_time;
	var $start_time;
	var $stop_time;
	var $est_start_time;
	var $est_stop_time;
	var $status = phase::STATUS_WAITING;
	var $maint_phase = false;

	/**
	 * The constructor of the phase class.
	 *
	 * @param i	 The ID of this phase.
	 * @return	 The newly initialized phase object.
	 */
	function __construct ($i) {
		parent::__construct ($i, "Phases");
	}

	/**
	 * Update the time approximation table.
	 * @author Jamie Dockendorff
	 *
	 * @param db		A database object.
	 * @param new_time	The new time for this phase.
	 * @return			The result of the database query.
	 */
	function update_time_approx ($db, $new_time) {
		$where = "keyword='" . $this->keyword . "'";
		$cols = array ("nexttime", "sigma");
		$result = $db->select_entry ("Timeapprox", $cols, $where);

		$arr = $result->fetch_assoc ();
		$est_time = (double)$arr['nexttime'];
		$sigma = (double)$arr['sigma'];

		$a = 1.0/($sigma*sqrt(2.0*3.1415));
		$b = 2.0 * pow ($sigma, 2.0);
		$c = pow (-($new_time-$est_time), 2);

		$peh = (double)$est_time*($a*pow (2.718, $c/$b));
		$pe = 0.5;
		$ph = $a;

		$phe = $peh * $ph / $pe;
		$est_time = ($phe * ($new_time - $est_time)) + $est_time;

		$cols = array ("nexttime" => $est_time);
		$result = $db->update_entry ("Timeapprox", $cols, $where);
		return $result;
	}

	/**
	 * Record the start time of an order.
	 *
	 * @param time	The time, or none to use the current time.
	 * @return		None
	 */
	function record_start_time ($time) {
		$db = new database ();

		$mach = new machine ($this->machId);
		$ret = $mach->init($db);
		if (!$ret)
			return FALSE;

		$result = $mach->update_status ($db, machine::STATUS_RUNNING);
		if (!$result) {
			$db->close ();
			return FALSE;
		}

		$this->status = phase::STATUS_RUNNING;
		$this->start_time = $time;
		$result = $this->update_database_by_id ($db);

		$db->close ();
		return TRUE;
	}

	/**
	 * Record the stop time of an order, and update the approximation table.
	 *
	 * @param db	A database object that is connected to the database.
	 * @param time	The time, or none to use the current time.
	 * @return		Whether or not the recording was successful.
	 */
	function record_stop_time ($time) {

		$db = new database ();

		$this->status = phase::STATUS_FINISHED;
		$this->stop_time = $time;
		$this->update_database_by_id ($db);

		$dt = new DateTime ($time);
		$stop = $dt->getTimeStamp ();

		// Set up start time as well.
		$dt = new DateTime ($this->start_time);
		$start = $dt->getTimeStamp ();

		$total = (int)(($stop - $start) / 60);

		//Divide by the quantity of the order.
		$order = new order ((int)$this->ordId);
		$ret = $order->init($db);
		if (!$ret)
			return NULL;

		$total /= $order->quantity;

		// Update the time approximation table.
		$result = $this->update_time_approx ($db, $total);
		if (!$result)
			return FALSE;

		// Update the machine's status.
		$mach = new machine ($this->machId);
		$ret = $mach->init($db);
		if (!$ret)
			return FALSE;

		$result = $mach->update_status ($db, machine::STATUS_IDLE);
		if (!$result)
			return FALSE;

		/* If this was a maintenance phase, then change the deadline. */

		$order = new order($this->ordId);
		$order->init($db);

		if ($order->maint_order()) {
			$result = $mach->update_maint ($db, $time);
			if (!$result)
				return FALSE;
			//TODO: Need to add an approximate time that the maintenance will take.
			$this->deadline = $time + $mach->time_between_maintenances + 120;
		} else {
			// If this isn't, then check to see if this is the final phase for the order.
			$num_phases = $order->num_phases ($db);
			if ($this->priority == $num_phases - 1)
				$order->complete ();
		}

		schedule::do_sched ($db);

		$db->close ();
		return TRUE;
	}

	/**
	 * Collect an array of materials for this phase.
	 *
	 * @param db	A database object that is connected to the database.
	 * @return		An array that associates material_id to quantity of the material needed.
	 */
	function collect_materials ($db = NULL) {
		if (!$db)
			$db = new database ();

		$cols = array ('mats_id', 'quantity');
		$result = $db->select_entry ('MaterialsperPhase', $cols, "phs_id=" . $this->id());
		$ret = array ();
		while ($arr = $result->fetch_assoc ()) {
			$mat_id = $arr['mats_id'];
			$qty = $arr['quantity'];
			$ret[$mat_id] = $qty;
		}
		return $ret;
	}

	/**
	 * Check whether or not all of the materials required for a phase are in stock.
	 *
	 * @param db	A database object that is connected to the database.
	 * @param arr	An associative array that maps material ids to the quantity.
	 * @return		None.
	 */
	function check_materials_with_arr ($db, $arr) {
		$price = 0.0;
		foreach ($arr as $id => $quant) {
			$mat = new material((int)$id);
			$mat->init ($db);
			if ((int)$mat->qty < (int)$quant) {
				print "Warning: not enough of material " . $mat->name . " in stock<br>\n";
			}
			$price += $mat->price * (int)$quant;
			$mat->qty -= (int)$quant;
			$result = $mat->update_database_by_id ($db);

			$ins = array (
				"mats_id" => $id,
				"phs_id" => $this->id(),
				"quantity" => $quant
			);

			$db->insert_entry ("MaterialsperPhase", $ins);
		}

		// Update the order price.
		$order = new order ($this->ordId);
		$order->init ($db);
		$order->price += $price;
		$result = $order->update_database_by_id ($db);
	}

	/**
	 * Check whether or not all of the materials required for a phase are in stock.
	 *
	 * @param db	A database object that is connected to the database.
	 */
	function check_materials ($db = NULL) {
		if (!$db)
			$db = new database ();

		$arr = collect_materials ();
		$this->check_materials_with_arr ($db, $arr);
	}

	/**
	 * Reads the approximate time for a phase given a keyword from the database.
	 *
	 * @param db		A database object that it connected to the database.
	 * @param keyword	The keyword used to determine the approximate time.
	 * @return			The approximate time that the phase should take.
	 */
	static function calculate_approx_time ($db, $keyword) {
		$where = "keyword='" . $keyword . "'";
		$cols = array ("nexttime");
		$result = $db->select_entry ("Timeapprox", $cols, $where);
		if (!$result) {
			return -1;
		}
		if ($result->num_rows == 0) {
			// If the keyword isn't in the database yet.
			$cols = array ("nexttime" => 1.0, "sigma" => 1, "keyword" => "$keyword");
			$result = $db->insert_entry ("Timeapprox", $cols);
			if (!$result) {
				return -1;
			} else {
				return 1;
			}
		} else {
			$arr = $result->fetch_assoc ();
			$nexttime = (double) $arr['nexttime'];
			return $nexttime;
		}
	}

	// This is directly included in order.php, since this
	// would create a circular dependency between orders and phases.
	/**
	 * Creates a maintenance phase.
	 *
	 * @param mach		The machine to add this to.
	 * @param order		The order object that owns this phase.
	 * @param db		A database object, or NULL for the default.
	 */
	static function create_maint_phase ($mach, $order, $db = NULL) {
		if (!$db)
			$db = new database ();

		$phaseId = 0;
		$phase = new phase ((int)$phaseId);
		$phase->machId = (int)$mach->id;
		$phase->ordId = (int)$order->id;

		$phase->priority = 0;

		$result = $phase->insert_to_database($db);
	}

	/**
	 * Correctly updates the database.
	 */
	function update_database_by_id ($db = NULL) {
		if (!$db)
			$db = new database ();

		$cols = array (
			"machId" => "'$this->machId'",
			"ordId" => "'$this->ordId'",
			"time_req" => "'$this->time_req'",
			"keyword" => "'$this->keyword'",
			"priority" => "'$this->priority'",
			"status" => "'$this->status'",
			"start_time" => "'$this->start_time'",
			"stop_time" => "'$this->stop_time'"
		);
		$result = $db->update_entry_by_id ("Phases", $cols, $this->id);
		return $result;
	}

	/**
	 * Correctly inserts a new phase into the database.
	 */
	function insert_to_database ($db = NULL) {
		if (!$db)
			$db = new database ();

		$cols = array (
			"machId" => $this->machId,
			"ordId" => $this->ordId,
			"time_req" => $this->time_req,
			"keyword" => "'$this->keyword'",
			"priority" => $this->priority,
			"status" => $this->status,
			"start_time" => "'$this->start_time'",
			"stop_time" => "'$this->stop_time'"
		);

		$result = $db->insert_entry ("Phases", $cols);
		if ($result)
			$this->id = $db->insert_id ();
		return $result;
	}

	/**
	 * Helper function to print a date/time entry.
	 *
	 * @param prefix	Either start or stop.
	 * @param entry		The type of entry, i.e. hour, minute, etc.
	 * @param array		The array of options to be printed out.
	 * @param cur		The current value for this entry.
	 */
	static function print_date_time_entry ($prefix, $entry, $array, $cur) {
		echo "<select name='$prefix-$entry'>\n";
		echo "<option value='-1'";
		if ($cur == -1) echo "selected";
		echo ">$entry</option>\n";

		foreach ($array as $num => $ent) {
			if (!strcmp($entry,'day') && $num == 0)
				continue;
			echo "<option value='$num'";
			if ($cur == $num)
				echo " selected";
			echo ">$ent</option>\n";
		}
		echo "</select>\n";
	}

	/**
	 * Print a date/time selection.
	 *
	 * @param prefix	Either start or stop time.
	 */
	function print_date_time ($prefix) {
		if (!strcmp ($prefix, "start")) $time = $this->start_time;
		else $time = $this->stop_time;

		if ($time) {
			$dt = new DateTime ($time);
			$t = $dt->getTimeStamp ();
			$date = getdate ($t);
		} else {
			$date = array (
				'mon' => -1,
				'mday' => -1,
				'year' => -1,
				'hours' => -1,
				'minutes' => -1
			);
		}

		$months = array (
			1 => "Jan", 2 => "Feb",
			3 => "Mar", 4 => "Apr",
			5 => "May", 6 => "Jun",
			7 => "Jul", 8 => "Aug",
			9 => "Sep", 10 => "Oct",
			11 => "Nov", 12 => "Dec"
		);

		$days = range(0,31);

		/* Get phase year, if any.
		   if none, use current year.
		 */
		$cur_year = $date['year'];
		if ($cur_year == -1) {
			// Set it to the current year.
			$time = time ();
			$d = getdate ($time);
			$cur_year = $d['year'];
		}

		$years = array (
			$cur_year => $cur_year,
			$cur_year + 1 => $cur_year + 1,
			$cur_year + 2 => $cur_year + 2
		);

		$hours = range(0,23);
		$minutes = range(0,59);

		echo "<tr>\n";
		echo "<td>" . ucwords ($prefix) . " Time</td>\n";
		echo "<td>\n";

		phase::print_date_time_entry ($prefix, "month", $months, (int)$date['mon']);
		phase::print_date_time_entry ($prefix, "day", $days, (int)$date['mday']);
		phase::print_date_time_entry ($prefix, "year", $years, (int)$date['year']);
		phase::print_date_time_entry ($prefix, "hour", $hours, (int)$date['hours']);
		phase::print_date_time_entry ($prefix, "min", $minutes, (int)$date['minutes']);
		echo "</td>
</tr>
";
	}

	/**
	 * Get the data from a date/time selection.
	 *
	 * @param post		The post data from the form.
	 * @param prefix	Either start/stop time.
	 */
	static function get_date_time ($post, $prefix) {
		$year	= (int)$post[$prefix . '-year'];
		$month	= (int)$post[$prefix . '-month'];
		$day	= (int)$post[$prefix . '-day'];
		$hour	= (int)$post[$prefix . '-hour'];
		$min	= (int)$post[$prefix . '-min'];

		$time = time ();
		$d = getdate ($time);

		if ($year == -1
			 && $month == -1
			 && $day == -1
			 && $hour == -1
			 && $min == -1)
			return NULL;

		$year	= ($year == -1) ? $d['year'] : $year;
		$month	= ($month == -1) ? $d['mon'] : $month;
		$day	= ($day == -1) ? $d['mday'] : $day;
		$hour	= ($hour == -1) ? $d['hours'] : $hour;
		$min	= ($min == -1) ? $d['minutes'] : $min;

		$t = mktime ($hour, $min, 0, $month, $day, $year);
		$ret = strftime ("%F %T", $t);
		return $ret;
	}

	/**
	 * Get the start and stop times as timestamps.
	 *
	 * @return	An array with the start and stop times.
	 */
	function get_time () {
		$start_t = $stop_t = NULL;

		if ($this->start_time) {
			$start_dt = new DateTime ($this->start_time);
			$start_t = (int)$start_dt->getTimeStamp();
		}

		if ($this->stop_time) {
			$stop_dt = new DateTime ($this->stop_time);
			$stop_t = (int)$stop_dt->getTimeStamp();
		}

		return array($start_t, $stop_t);
	}
}
?>